import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


/**
 * Fill in class
 * 
 * @author Tobin Chee
 *
 */
public class LinkingSymbolTable {
	
	/** List containing labels for all defined symbols.
	 */
	private List<String> labels = new ArrayList<String>();
	/** List containing address for all defined symbols.
	 */
	private List<Integer> addresses = new ArrayList<Integer>();
	/** List containing usages for all defined symbols.
	 */
	private List<LinkingSymbol.SymbolUsage> usages = new ArrayList<LinkingSymbol.SymbolUsage>();
	/** List containing module names containing all defined symbols.
	 */
	private List<String> moduleNames = new ArrayList<String>();
	
	/**
	 * Default constructor
	 */
	public LinkingSymbolTable(){
	}
	
	public LinkingSymbolTable(LinkingSymbol symbol){
		this.defineSymbol(symbol);
	}
	
	/** 
	 * Sorts the symbol table in alphabetical order by label.
	 * 
	 * @author Tobin Chee
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public void sort(){
		List<String> lbls = new ArrayList<String>(this.labels);
		List<Integer> addrs = new ArrayList<Integer>(this.addresses);
		this.addresses.clear();
		List<LinkingSymbol.SymbolUsage> uses = new ArrayList<LinkingSymbol.SymbolUsage>(this.usages);
		usages.clear();
		
		Collections.sort(labels);
		
		for(String lbl: labels){
			int index = lbls.indexOf(lbl);
			
			lbls.remove(index);
			addresses.add(addrs.remove(index));
			usages.add(uses.remove(index));
		}
	}
	
	/**
	 * Writes the sorted contents of symbolTable to file.
	 * 
	 * @param out file to be written to.
	 * @throws IOException if an I/O error occurs.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public void putToFile(BufferedWriter out) throws IOException{
		
		this.sort();
		String temp;
		//header
		out.write("Label\t\t\t\t\taddress\tusage\t\t\tmodule name\n");
		
		for(int i = 0; i < labels.size(); i++){
			//Label
			temp = labels.get(i);
			out.write(temp);
			putTabsToFile(out, 5, temp.length());
			
			//address
			temp = Integer.toHexString(addresses.get(i));
			out.write(temp);
			putTabsToFile(out, 2, temp.length());
			//usage
			temp = usages.get(i).toString();
			out.write(temp);
			putTabsToFile(out, 3, temp.length());
			//module name
			out.write(moduleNames.get(i) + "\n");
		}
	}
	
	/**
	 * Calculates and outputs the correct number of tabs based on the width of a formatted column (width in tabs)
	 * and the length of the string output in the column.
	 * 
	 * @param reportFile the file this method writes to.
	 * @param columnWidth the width of a formatted column in tabs.
	 * @param stringLength the length of the string output in the column.
	 * 
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	private void putTabsToFile(BufferedWriter out, Integer columnWidth, Integer stringLength) throws IOException{
		
		final int tabWidth = 6;
		int tabs = ((columnWidth * tabWidth) - stringLength) / tabWidth;
		if((stringLength % tabWidth) != 0){
			tabs++;
		}
		for(int j = 0; j < tabs; j++){
			out.write("\t");
		}
	}
	
	/**
	 * Defines a new symbol in SymbolTable.
	 * 
	 * @param symbol a symbol.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public void defineSymbol(LinkingSymbol symbol){
		labels.add(symbol.getLabel());
		addresses.add(symbol.getAddress());
		usages.add(symbol.getUsage());
		moduleNames.add(symbol.getModuleName());
	}
	
	/**
	 * Updates symbol label of symbol to lbl at the designated index.
	 * 
	 * @param lbl symbol label.
	 * @param index index of symbol.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public void updateLabel(String lbl, int index){
		labels.set(index, lbl);
	}
	
	/**
	 * Returns symbol label of symbol at the designated index.
	 * 
	 * @param index index of symbol.
	 * @return symbol label.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public String getLabel(int index){
		return labels.get(index);
	}
	
	/**
	 * Updates address value of symbol to addr at the designated index.
	 * 
	 * @param addr symbol address.
	 * @param index index of symbol.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public void updateAddress(int addr, int index){
		addresses.set(index, addr);
	}
	
	/**
	 * Returns address of symbol at the designated index.
	 * 
	 * @param index index of symbol.
	 * @return symbol address.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public int getAddress(int index){
		return addresses.get(index);
	}
	
	/**
	 * Updates symbol usage to use at the designated index.
	 * 
	 * @param use symbol usage.
	 * @param index index of symbol.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public void updateUsage(LinkingSymbol.SymbolUsage use, int index){
		usages.set(index, use);
	}
	
	/**
	 * Returns symbol usage of symbol at the designated index.
	 * 
	 * @param index index of symbol.
	 * @return symbol usage.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public LinkingSymbol.SymbolUsage getUsage(int index){
		return usages.get(index);
	}
	
	/**
	 * Updates symbol module name to modName at the designated index.
	 * 
	 * @param modName module name.
	 * @param index index of symbol.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public void updateModuleName(String modName, int index){
		moduleNames.set(index, modName);
	}
	
	/**
	 * Returns symbol module name of the symbol at the designated index.
	 * 
	 * @param index symbol index.
	 * @return module name.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public String getModuleName(int index){
		return moduleNames.get(index);
	}
	
	/**
	 * Determines if symbol table contains the designated label.
	 * <p>
	 * Symbol table is considered to contain lbl, if lbl equals the symbol
	 * label of any symbol in the symbol table.
	 * 
	 * @param lbl symbol label.
	 * @return true, if the symbol table contains lbl; false, otherwise.
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public boolean contains(String lbl){
		return labels.contains(lbl);
	}
	
	/**
	 * Requires that lbl exists in SymbolTable.
	 * 
	 * @param lbl symbol label.
	 * @return index of symbol in symbol table when symbol label == lbl
	 * 
	 * Change Log:<br>
	 * 	- No changes.
	 */
	public int indexOf(String lbl){
		return labels.indexOf(lbl);
	}
	
	/**
	 * Returns the number of symbols (records) in the symbol table.
	 * 
	 * @return number of symbols in the symbol table.
	 * @author Tobin Chee
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public int size(){
		return labels.size();
	}
	
	
}
